package top.zxybird.yun.serialization.thrift;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import javax.annotation.Nullable;

import org.springframework.beans.BeanUtils;

import com.google.common.base.Function;

import org.springframework.util.CollectionUtils;
import top.zxybird.yun.serialization.thrift.model.*;

/**
 * 由原始Java类型转化成TType类型
 */
public class Object2TType {

  private enum Type {
    /**
     * byte
     */
    _byte(byte.class, new Function<Object, TByte>() {
      @Nullable
      @Override
      public TByte apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TByte((Byte) input);
      }
    }),
    /**
     * Byte
     */
    _Byte(Byte.class, _byte.function),
    /**
     * boolean
     */
    _boolean(boolean.class, new Function<Object, TBoolean>() {
      @Nullable
      @Override
      public TBoolean apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TBoolean((Boolean) input);
      }
    }),
    /**
     * Boolean
     */
    _Boolean(Boolean.class, _boolean.function),
    /**
     * short
     */
    _short(short.class, new Function<Object, TShort>() {
      @Nullable
      @Override
      public TShort apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TShort((Short) input);
      }
    }),
    /**
     * Short
     */
    _Short(Short.class, _short.function),
    /**
     * int
     */
    _int(int.class, new Function<Object, TInt>() {
      @Nullable
      @Override
      public TInt apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TInt((Integer) input);
      }
    }),
    /**
     * Integer
     */
    _Integer(Integer.class, _int.function),
    /**
     * long
     */
    _long(long.class, new Function<Object, TLong>() {
      @Nullable
      @Override
      public TLong apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TLong((Long) input);
      }
    }),
    /**
     * Long
     */
    _Long(Long.class, _long.function),
    /**
     * double
     */
    _double(double.class, new Function<Object, TDouble>() {
      @Nullable
      @Override
      public TDouble apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TDouble((Double) input);
      }
    }),
    /**
     * Double
     */
    _Double(Double.class, _double.function),
    /**
     * String
     */
    _String(String.class, new Function<Object, TString>() {
      @Nullable
      @Override
      public TString apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }
        return new TString((String) input);
      }
    }),
    /**
     * Set
     */
    _Set(Set.class, new Function<Object, TSet>() {
      @Nullable
      @Override
      public TSet apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }

        Set _set = (Set) input;
        if (CollectionUtils.isEmpty(_set)) {
          return null;
        }

        TSet out = new TSet();

        for (Object obj : _set) {
          out.add(findByTypeWithDefault(obj.getClass()).function.apply(obj));
        }

        return out;
      }
    }),
    /**
     * List
     */
    _List(List.class, new Function<Object, TList>() {
      @Nullable
      @Override
      public TList apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }

        List _in = (List) input;
        if (CollectionUtils.isEmpty(_in)) {
          return null;
        }

        TList out = new TList();

        for (Object obj : _in) {
          out.add(findByTypeWithDefault(obj.getClass()).function.apply(obj));
        }

        return out;
      }
    }),
    /**
     * List
     */
    _Map(Map.class, new Function<Object, TMap>() {
      @Nullable
      @Override
      public TMap apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }

        Map _in = (Map) input;
        if (CollectionUtils.isEmpty(_in)) {
          return null;
        }

        TMap out = new TMap();

        for (Object obj : _in.entrySet()) {
          Map.Entry entry = (Map.Entry) obj;
          out.put(findByTypeWithDefault(entry.getKey().getClass()).function.apply(entry.getKey()),
              findByTypeWithDefault(entry.getValue().getClass()).function.apply(entry.getValue()));
        }

        return out;
      }
    }),
    /**
     * Object
     */
    _Object(Object.class, new Function<Object, TType>() {
      @Nullable
      @Override
      public TType apply(@Nullable Object input) {
        if (null == input) {
          return null;
        }

        TStruct struct = new TStruct();

        List<PropertyDescriptor> propertyDescriptorList = Arrays
            .stream(BeanUtils.getPropertyDescriptors(input.getClass())).filter(x -> x.getPropertyType() != Class.class)
            .collect(Collectors.toList());

        IntStream.range(0, propertyDescriptorList.size()).forEach(index -> {
          PropertyDescriptor p = propertyDescriptorList.get(index);
          Method readMethod = p.getReadMethod();
          Class clazz = p.getPropertyType();
          Object obj;
          try {
            obj = readMethod.invoke(input);
            TType type;
            if (null != obj) {
              type = findByTypeWithDefault(obj.getClass()).function.apply(obj);
              if (null != type) {
                type.setNumber((short) (index + 1));
              }
            } else {
              type = null;
            }
            struct.addField(type);
          } catch (Throwable e) {
            e.printStackTrace();
          }
        });

        return struct;
      }
    });

    private Class clazz;
    private Function<Object, ? extends TType> function;

    @SuppressWarnings("unchecked")
    Type(Class clazz, Function function) {
      this.clazz = clazz;
      this.function = function;
    }
  }

  private static Map<Class, Type> typeMap = Arrays.stream(Type.values())
      .collect(Collectors.toMap(x -> x.clazz, x -> x, (o1, o2) -> o1));

  private static Type findByTypeWithDefault(Class<?> clazz) {

    if (null == clazz) {
      return null;
    }

    // 对Set、List、Map做特殊处理
    if (Set.class.isAssignableFrom(clazz)) {
      return Type._Set;
    }

    if (List.class.isAssignableFrom(clazz)) {
      return Type._List;
    }

    if (Map.class.isAssignableFrom(clazz)) {
      return Type._Map;
    }

    Type type = typeMap.get(clazz);

    if (null != type) {
      return type;
    } else {
      return Type._Object;
    }

  }

  /**
   * 对外接口
   */
  public static TType transform(Object in) {
    return findByTypeWithDefault(in.getClass()).function.apply(in);
  }

}
